/* $Id: mib.c,v 1.16 2005/10/10 05:21:00 jfb Exp $ */
/*
 * Copyright (c) 2003 Jean-Francois Brousseau <phrakt@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/types.h>

#include <errno.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

#include <canopy/mem.h>
#include <canopy/queue.h>

#include "log.h"
#include "mib.h"
#include "string.h"
#include "private.h"


#define CNP_MIB_OID_GETLEN(o)	((uint8_t *)&(o))[7]
#define CNP_MIB_OID_GETID(o, i)	((uint8_t *)&(o))[i]

#define CNP_MIB_OID_SETLEN(o, l)					\
	do {								\
		uint8_t *_slop = (uint8_t *)&(o);			\
		if ((l) <= CNP_MIB_MAXOIDLEN)				\
			_slop[7] = l;					\
	} while (0)

#define CNP_MIB_OID_SETID(o, i, v)					\
	do {								\
		uint8_t *_siop = (uint8_t *)&(o);			\
		if ((i) < CNP_MIB_MAXOIDLEN) {				\
			_siop[i] = (v);					\
			if ((i) >= CNP_MIB_OID_GETLEN(o)) {		\
				CNP_MIB_OID_SETLEN(o, (i) + 1);		\
			}						\
		}							\
	} while (0)



typedef struct cnp_mib_node {
	int                mn_id;
	char               mn_name[CNP_MIB_MAXNAMELEN];
	enum cnp_mib_type  mn_type;
	enum cnp_mib_mode  mn_mode;
	int                mn_flags;

	time_t             mn_mtime;	/* last modification time */

	union {
		char      *sval;
		uint32_t   ival;

		struct cnp_mib_branch {
			int                   mb_childcnt;
			struct cnp_mib_node **mb_children;
		} branch;
	} mn_val;

	/* use this instead if variable is bound */
	void   *mn_binding;
	size_t  mn_bndlen;

	struct cnp_mib_handler  *mn_hdlrs;
	struct cnp_mib_node     *mn_parent;

} cnp_mib_node_t;

#define mn_childcnt    mn_val.branch.mb_childcnt
#define mn_children    mn_val.branch.mb_children



struct cnp_mib {
	int               mib_flags;
	cnp_mib_node_t   *mib_root;

	pthread_rwlock_t  mib_rwlock;
};


struct cnp_mib_handler {
	cnp_mib_hdlr hdlr;
	void  *arg;
	struct cnp_mib_handler *h_next;
};




#if 0
static const char *mib_node_types[] = {
	"",
	"branch",
	"boolean",
	"string",
	"integer",
	"unsigned integer",
};
#endif


static cnp_mib_node_t* cnp_mib_getbyoid  (cnp_mib_t *, cnp_oid_t);
static cnp_mib_node_t* cnp_mib_getbypath (cnp_mib_t *, const char *);

static cnp_mib_node_t* cnp_mib_newnode   (cnp_mib_t *, enum cnp_mib_type,
						enum cnp_mib_mode,
						const char *, cnp_mib_node_t *);
static void            cnp_mib_freenode  (cnp_mib_node_t *);
static int             cnp_mib_statnode  (cnp_mib_t *, cnp_mib_node_t *,
						struct cnp_mib_stat *);
static int             cnp_mib_bindnode  (cnp_mib_t *, cnp_mib_node_t *,
						void *, size_t);
static int             cnp_mib_unbindnode(cnp_mib_t *, cnp_mib_node_t *);



/*
 * cnp_mib_alloc()
 *
 * Allocate a new Management Information Base structure and initialize it.
 * Returns a pointer to the MIB on success, or NULL on failure.
 */
cnp_mib_t*
cnp_mib_alloc(void)
{
	int ret;
	cnp_mib_t *mib;

	if ((mib = cnp_malloc(sizeof(*mib))) == NULL) {
		cnp_log(cnp_logchan, CNP_LOG_ERRNO, "failed to allocate MIB");
		return (NULL);
	}
	memset(mib, 0, sizeof(*mib));

	mib->mib_root = cnp_mib_newnode(mib, CNP_MIB_TYPE_BRANCH,
	    CNP_MIB_MODE_RW, "", NULL);
	if (mib->mib_root == NULL) {
		cnp_log(cnp_logchan, CNP_LOG_ERR,
		    "failed to allocate root MIB node");
		cnp_free(mib);
		return (NULL);
	}

	ret = pthread_rwlock_init(&mib->mib_rwlock, NULL);
	if (ret != 0) {
		cnp_log(cnp_logchan, CNP_LOG_ERR,
		    "failed to initialize MIB lock: %s", strerror(ret));
		cnp_mib_freenode(mib->mib_root);
		cnp_free(mib);
		return (NULL);
	}

	return (mib);
}

/*
 * cnp_mib_free()
 *
 * Free the Management Information Base <mib> and all its associated resources.
 */
void
cnp_mib_free(cnp_mib_t *mib)
{
	int ret;

	if ((ret = pthread_rwlock_wrlock(&mib->mib_rwlock)) != 0) {
	}

	cnp_mib_freenode(mib->mib_root);

	(void)pthread_rwlock_unlock(&mib->mib_rwlock);
	(void)pthread_rwlock_destroy(&mib->mib_rwlock);

	cnp_free(mib);
}

/*
 * cnp_mib_mknode()
 *
 * Create a new node
 */
int
cnp_mib_mknode(cnp_mib_t *mib, const char *path, enum cnp_mib_type type,
    enum cnp_mib_mode mode, int flags)
{
	int ret;
	char buf[CNP_MIB_MAXPATHLEN], *name = NULL;
	cnp_mib_node_t *node, *parent;

	if (strlcpy(buf, path, sizeof(buf)) >= sizeof(buf)) {
		cnp_log(cnp_logchan, CNP_LOG_ERR, "failed to register MIB path "
		    "%s: Path too long", path);
		return (-1);
	}

	if ((ret = pthread_rwlock_wrlock(&mib->mib_rwlock)) != 0) {
		cnp_log(cnp_logchan, CNP_LOG_ERR,
		    "failed to acquire write lock on MIB");
		return (-1);
	}

	if ((name = strrchr(buf, '.')) == NULL) {
		parent = mib->mib_root;
		name = buf;
	} else {
		*(name++) = '\0';
		parent = cnp_mib_getbypath(mib, buf);
		if (parent == NULL) {
			cnp_log(cnp_logchan, CNP_LOG_ERR,
			    "failed to lookup parent node `%s'", buf);
			(void)pthread_rwlock_unlock(&mib->mib_rwlock);
			return (-1);
		}
	}

	if ((node = cnp_mib_newnode(mib, type, mode, name, parent)) == NULL)
		ret = -1;

	(void)pthread_rwlock_unlock(&mib->mib_rwlock);

	return (ret);
}

/*
 * cnp_mib_stat()
 *
 */
int
cnp_mib_stat(cnp_mib_t *mib, const char *path, struct cnp_mib_stat *st)
{
	int ret;
	cnp_mib_node_t *node;

	if ((ret = pthread_rwlock_rdlock(&mib->mib_rwlock)) != 0) {
		cnp_log(cnp_logchan, CNP_LOG_ERR,
		    "failed to acquire read lock on MIB");
		return (-1);
	}

	if ((node = cnp_mib_getbypath(mib, path)) == NULL) {
		cnp_log(cnp_logchan, CNP_LOG_ERR,
		    "failed to stat node `%s': No such node", path);
		ret = -1;
	}
	else
		ret = cnp_mib_statnode(mib, node, st);

	(void)pthread_rwlock_unlock(&mib->mib_rwlock);

	return (ret);
}

/*
 * cnp_mib_oid_stat()
 *
 */
int
cnp_mib_oid_stat(cnp_mib_t *mib, cnp_oid_t oid, struct cnp_mib_stat *st)
{
	int ret;
	cnp_mib_node_t *node;

	if ((ret = pthread_rwlock_rdlock(&mib->mib_rwlock)) != 0) {
		cnp_log(cnp_logchan, CNP_LOG_ERR,
		    "failed to acquire read lock on MIB");
		return (-1);
	}

	if ((node = cnp_mib_getbyoid(mib, oid)) == NULL)
		ret = -1;
	else
		ret = cnp_mib_statnode(mib, node, st);

	(void)pthread_rwlock_unlock(&mib->mib_rwlock);

	return (ret);
}

/*
 * cnp_mib_statnode()
 *
 */
static int
cnp_mib_statnode(cnp_mib_t *mib, cnp_mib_node_t *node,
    struct cnp_mib_stat *st)
{
	st->ms_type = node->mn_type;
	st->ms_mode = node->mn_mode;
	st->ms_size = 0;
	st->ms_mtime = node->mn_mtime;

	return (0);
}


/*
 * cnp_mib_addnode()
 *
 * Attach a node as a child of the node whose OID is <poid>.
 * Returns the child's OID on success, or CNP_MIB_OID_INVAL on failure.
 */
cnp_oid_t
cnp_mib_addnode(cnp_mib_t *mib, cnp_oid_t poid, const char *name,
    enum cnp_mib_type type, enum cnp_mib_mode mode, int flags)
{
	cnp_oid_t ret;
	cnp_mib_node_t *parent, *node;

	if ((parent = cnp_mib_getbyoid(mib, poid)) == NULL)
		ret = CNP_MIB_OID_INVAL;
	else if (parent->mn_type != CNP_MIB_TYPE_BRANCH) {
		ret = CNP_MIB_OID_INVAL;
	}
	else if (parent->mn_childcnt == CNP_MIB_MAXCHLD) {
		ret = CNP_MIB_OID_INVAL;
	}

	if ((node = cnp_mib_newnode(mib, type, mode, name, parent)) == NULL)
		return (CNP_MIB_OID_INVAL);

	ret = poid;
	CNP_MIB_OID_SETID(ret, (int)CNP_MIB_OID_GETLEN(poid),
	    parent->mn_childcnt);

	return (ret);
}

/*
 * cnp_mib_bind()
 *
 * Bind the MIB node whose path is specified in <path> to the memory area
 * pointed to by <var>, which is of size <vlen>.
 * When such a binding has been performed on a variable, it becomes the storage
 * area for node's value, thus any changes on that node's value are directly
 * changed in the variable and vice versa.
 * Returns 0 on success, or -1 on failure.
 */
int
cnp_mib_bind(cnp_mib_t *mib, const char *path, void *var, size_t vlen)
{
	int ret = 0;
	cnp_mib_node_t *node;

	if ((ret = pthread_rwlock_wrlock(&mib->mib_rwlock)) != 0) {
		cnp_log(cnp_logchan, CNP_LOG_ERR,
		    "failed to acquire write lock on MIB");
		return (-1);
	}

	if ((node = cnp_mib_getbypath(mib, path)) == NULL) {
		cnp_log(cnp_logchan, CNP_LOG_ERR,
		    "failed to bind node %s: No such node", path);
		ret = -1;
	}
	else
		ret = cnp_mib_bindnode(mib, node, var, vlen);

	(void)pthread_rwlock_unlock(&mib->mib_rwlock);

	return (ret);
}

/*
 * cnp_mib_oid_bind()
 *
 * Bind the MIB node whose path is specified in <path> to the memory area
 * pointed to by <var>, which is of size <vlen>.
 * When such a binding has been performed on a variable, it becomes the storage
 * area for node's value, thus any changes on that node's value are directly
 * changed in the variable and vice versa.
 * Returns 0 on success, or -1 on failure.
 */
int
cnp_mib_oid_bind(cnp_mib_t *mib, cnp_oid_t oid, void *var, size_t vlen)
{
	int ret;
	cnp_mib_node_t *node;

	if ((ret = pthread_rwlock_wrlock(&mib->mib_rwlock)) != 0) {
		cnp_log(cnp_logchan, CNP_LOG_ERR,
		    "failed to acquire write lock on MIB");
		return (-1);
	}

	if ((node = cnp_mib_getbyoid(mib, oid)) == NULL) {
		ret = -1;
	}
	else
		ret = cnp_mib_bindnode(mib, node, var, vlen);

	(void)pthread_rwlock_unlock(&mib->mib_rwlock);

	return (ret);
}

/*
 * cnp_mib_bindnode()
 *
 * Bind the MIB node whose path is specified in <path> to the memory area
 * pointed to by <var>, which is of size <vlen>.
 * When such a binding has been performed on a variable, it becomes the storage
 * area for node's value, thus any changes on that node's value are directly
 * changed in the variable and vice versa.
 * Returns 0 on success, or -1 on failure.
 */
static int
cnp_mib_bindnode(cnp_mib_t *mib, cnp_mib_node_t *node, void *var,
    size_t vlen)
{
	int ret = 0;

	cnp_log(cnp_logchan, CNP_LOG_DEBUG,
	    "binding node %p to memory area %p (len=%u)",
	    node, var, (unsigned int)vlen);
	if (node->mn_type == CNP_MIB_TYPE_BRANCH) {
		cnp_log(cnp_logchan, CNP_LOG_ERR, "cannot bind a branch node");
		ret = -1;
	}
	else if (node->mn_flags & CNP_MIB_FL_BOUND) {
		cnp_log(cnp_logchan, CNP_LOG_ERR, "node is already bound");
		ret = -1;
	}
	else {
		node->mn_flags |= CNP_MIB_FL_BOUND;
		node->mn_binding = var;
		node->mn_bndlen = vlen;
	}

	return (ret);
}

/*
 * cnp_mib_unbind()
 *
 * Unbind the MIB variable whose corresponding OID is given in <oid>.
 * Returns 0 on success, or -1 on failure.
 */
int
cnp_mib_unbind(cnp_mib_t *mib, const char *path)
{
	int ret = 0;
	cnp_mib_node_t *node;

	if ((ret = pthread_rwlock_wrlock(&mib->mib_rwlock)) != 0) {
		cnp_log(cnp_logchan, CNP_LOG_ERR,
		    "failed to acquire write lock on MIB");
		return (-1);
	}

	if ((node = cnp_mib_getbypath(mib, path)) == NULL)
		ret = -1;
	else
		ret = cnp_mib_unbindnode(mib, node);

	(void)pthread_rwlock_unlock(&mib->mib_rwlock);

	return (ret);
}

/*
 * cnp_mib_oid_unbind()
 *
 * Unbind the MIB variable whose corresponding OID is given in <oid>.
 * Returns 0 on success, or -1 on failure.
 */
int
cnp_mib_oid_unbind(cnp_mib_t *mib, cnp_oid_t oid)
{
	int ret = 0;
	cnp_mib_node_t *node;

	if ((ret = pthread_rwlock_wrlock(&mib->mib_rwlock)) != 0) {
		cnp_log(cnp_logchan, CNP_LOG_ERR,
		    "failed to acquire write lock on MIB");
		return (-1);
	}

	if ((node = cnp_mib_getbyoid(mib, oid)) == NULL)
		ret = -1;
	else
		ret = cnp_mib_unbindnode(mib, node);

	(void)pthread_rwlock_unlock(&mib->mib_rwlock);

	return (ret);
}

/*
 * cnp_mib_unbindnode()
 *
 * Unbind the MIB variable whose corresponding OID is given in <oid>.
 * Returns 0 on success, or -1 on failure.
 */
static int
cnp_mib_unbindnode(cnp_mib_t *mib, cnp_mib_node_t *node)
{
	int ret = 0;

	if (!(node->mn_flags & CNP_MIB_FL_BOUND)) {
		ret = -1;
	}
	else {
		node->mn_flags &= ~CNP_MIB_FL_BOUND;
		node->mn_binding = NULL;
		node->mn_bndlen = 0;
	}

	return (ret);
}

/*
 * cnp_mib_get()
 *
 * Retrieve the value associated with the object ID <oid>, and store it into
 * the variable <dst>, which is of size <len>.  Appropriate typecasting and
 * dereferencing is done depending on the variable type.  This
 * function assumes that it is passed the right argument type by the
 * programmer.  Otherwise, this is likely to cause weird behaviour.
 * Returns the datatype of the variable on success, and -1 on failure.
 */
int
cnp_mib_get(cnp_mib_t *mib, const char *path, void *dst, size_t len)
{
	int ret;
	char *dststr;
	cnp_mib_node_t *node;

	if ((ret = pthread_rwlock_rdlock(&mib->mib_rwlock)) != 0) {
		cnp_log(cnp_logchan, CNP_LOG_ERR,
		    "failed to acquire read lock on MIB");
		return (-1);
	}

	if ((node = cnp_mib_getbypath(mib, path)) == NULL) {
		(void)pthread_rwlock_unlock(&mib->mib_rwlock);
		return (-1);
	}

	switch (node->mn_type) {
	case CNP_MIB_TYPE_BOOL:
		*(int *)dst = (node->mn_val.ival) ? 1 : 0;
		break;
	case CNP_MIB_TYPE_STRING:
		dststr = (char *)dst;
		if (IS_BOUND(node) && (node->mn_binding != NULL))
			strlcpy(dststr, (char *)node->mn_binding, len);
		else if (node->mn_val.sval != NULL)
			strlcpy(dststr, node->mn_val.sval, len);
		else
			dststr[0] = '\0';
		break;
	case CNP_MIB_TYPE_INT:
		if (IS_BOUND(node))
			*(int *)dst = *(int *)node->mn_binding;
		else
			*(int *)dst = node->mn_val.ival;
		break;
	case CNP_MIB_TYPE_UINT:
		if (IS_BOUND(node))
			*(int *)dst = *(int *)node->mn_binding;
		else
			*(int *)dst = node->mn_val.ival;
		break;
	case CNP_MIB_TYPE_BRANCH:
		ret = -1;
		break;
	default:
		*(int *)dst = (0);
	}

	(void)pthread_rwlock_unlock(&mib->mib_rwlock);

	return (ret);
}

/*
 * cnp_mib_set()
 *
 * Sets the value of the MIB variable associated with object ID <oid> to
 * <val>, which is of size <vlen>, and is typecast depending on the type of the
 * variable.  If a handler is set for this variable, it gets run at the end
 * of the function, with the proper arguments.  Handler functions are better
 * described in the documentation of cnp_mib_action().
 *
 * Expected argument types:
 *	CNP_MIB_TYPE_BRANCH	-> none (read-only)
 *	CNP_MIB_TYPE_INT	-> int *
 *	CNP_MIB_TYPE_UINT	-> unsigned int *
 *	CNP_MIB_TYPE_STRING	-> char *
 *	CNP_MIB_TYPE_BOOL	-> unsigned int * (any non-zero value is TRUE)
 *
 * Returns 0 on success or -1 on failure.
 */

int
cnp_mib_set(cnp_mib_t *mib, const char *path, const void *val, size_t vlen)
{
	int ret;
	char *dc;
	cnp_oid_t oid;
	cnp_mib_node_t *node;
	struct cnp_mib_handler *hdlr;

	if (cnp_mib_pathtooid(mib, path, &oid) == -1)
		return (-1);

	if ((ret = pthread_rwlock_wrlock(&mib->mib_rwlock)) != 0) {
		cnp_log(cnp_logchan, CNP_LOG_ERR,
		    "failed to acquire write lock on MIB");
		return (-1);
	}

	if ((node = cnp_mib_getbypath(mib, path)) == NULL) {
		cnp_log(cnp_logchan, CNP_LOG_ERR,
		    "failed to stat node `%s': No such node", path);
		(void)pthread_rwlock_unlock(&mib->mib_rwlock);
		return (-1);
	}

	if (node->mn_mode == CNP_MIB_MODE_RO) {
		cnp_log(cnp_logchan, CNP_LOG_ERR,
		    "failed to set value on read-only node `%s'", path);
		(void)pthread_rwlock_unlock(&mib->mib_rwlock);
		return (-1);
	}

	switch (node->mn_type) {
	case CNP_MIB_TYPE_BRANCH:
		cnp_log(cnp_logchan, CNP_LOG_ERR,
		    "cannot set value on branch node `%s'", path);
		ret = -1;
		break;

	case CNP_MIB_TYPE_BOOL:
		if (IS_BOUND(node))
			*(int *)node->mn_binding =
			    (*((const int *)val) == 0) ? 0 : 1;
		else
			node->mn_val.ival =
			    (*((const int *)val) == 0) ? 0 : 1;
		break;

	case CNP_MIB_TYPE_STRING:
		if (IS_BOUND(node))
			strlcpy((char *)node->mn_binding,
			    (const char *)val, node->mn_bndlen);
		else {
			if ((dc = strdup((const char *)val)) == NULL) {
				cnp_log(cnp_logchan, CNP_LOG_ERR,
				    "failed to copy string value for node `%s'",
				    path);
				ret = -1;
			} else {
				if (node->mn_val.sval != NULL)
					cnp_free(node->mn_val.sval);
				node->mn_val.sval = dc;
			}
		}
		break;

	case CNP_MIB_TYPE_INT:
	case CNP_MIB_TYPE_UINT:
		if (IS_BOUND(node))
			*(int *)node->mn_binding = *(const int *)val;
		else
			node->mn_val.ival = *((const int *) val);
		break;

	default:
		cnp_log(cnp_logchan, CNP_LOG_ERR, "invalid node type");
		ret = -1;
		break;
	}

	for (hdlr = node->mn_hdlrs; hdlr != NULL; hdlr = hdlr->h_next)
		hdlr->hdlr(mib, oid, hdlr->arg);

	(void)pthread_rwlock_unlock(&mib->mib_rwlock);

	return (ret);
}

/*
 * cnp_mib_traverse()
 *
 * Traverse the MIB recursively, starting at the node whose path is specified
 * in <top>, and calling the handler <hdlr> for each node encountered.
 */
int
cnp_mib_traverse(cnp_mib_t *mib, cnp_oid_t top, cnp_mib_hdlr hdlr, void *arg)
{
	int ret;
	int i, len;
	cnp_oid_t oid;
	cnp_mib_node_t *node;

	if ((ret = pthread_rwlock_rdlock(&mib->mib_rwlock)) != 0) {
		cnp_log(cnp_logchan, CNP_LOG_ERR,
		    "failed to acquire read lock on MIB");
		return (-1);
	}

	if ((node = cnp_mib_getbyoid(mib, top)) == NULL) {
		(void)pthread_rwlock_unlock(&mib->mib_rwlock);
		return (-1);
	}

	oid = top;
	ret = 0;

	/* first, run it for the top node */
	if ((ret = (*hdlr)(mib, oid, arg)) < 0) {
		(void)pthread_rwlock_unlock(&mib->mib_rwlock);
		return (-1);
	}

	len = CNP_MIB_OID_GETLEN(oid);
	for (i = 0; i < node->mn_childcnt; i++) {
		CNP_MIB_OID_SETID(oid, len, i);
		if (node->mn_children[i]->mn_type == CNP_MIB_TYPE_BRANCH)
			cnp_mib_traverse(mib, oid, hdlr, arg);
		else if ((ret = (*hdlr)(mib, oid, arg)) < 0)
			break;
	}

	(void)pthread_rwlock_unlock(&mib->mib_rwlock);

	return (ret);
}

/*
 * cnp_mib_pathtooid()
 *
 * Converts a MIB path to the associated object ID path.
 * Returns the length of the object ID stored in <oid>.
 * If an error occured (i.e. <path> is NULL), -1 is returned.
 */
int
cnp_mib_pathtooid(cnp_mib_t *mib, const char *path, cnp_oid_t *oid)
{
	int ret, i, depth;
	char pathbuf[CNP_MIB_MAXPATHLEN], *vsp, *name;
	cnp_mib_node_t *node;

	if (strlcpy(pathbuf, path, sizeof(pathbuf)) >= sizeof(pathbuf)) {
		cnp_log(cnp_logchan, CNP_LOG_ERR,
		    "failed to convert path `%s' to OID: Path too long", path);
		return (-1);
	}

	if ((ret = pthread_rwlock_rdlock(&mib->mib_rwlock)) != 0) {
		cnp_log(cnp_logchan, CNP_LOG_ERR,
		    "failed to acquire read lock on MIB");
		return (-1);
	}

	node = mib->mib_root;

	vsp = pathbuf;

	/* skip the root delimiter if any */
	if (*vsp == CNP_MIB_DELIM)
		vsp++;
	name = vsp;
	depth = 0;

	while (name != NULL) {
		vsp = strchr(vsp, CNP_MIB_DELIM);
		if (vsp != NULL)
			*(vsp++) = '\0';

		for (i = 0; i < node->mn_childcnt; i++) {
			if (strcmp(name, node->mn_children[i]->mn_name) == 0) {
				node = node->mn_children[i];
				CNP_MIB_OID_SETID(*oid, depth, node->mn_id);
				depth++;
				break;
			}
		}

		if (i == node->mn_childcnt) {
			*oid = CNP_MIB_OID_INVAL;
			ret = -1;
			break;
		}

		name = vsp;
	}

	(void)pthread_rwlock_unlock(&mib->mib_rwlock);

	return (ret);
}

/*
 * cnp_mib_oidtopath()
 *
 * Generate the MIB path that corresponds to the OID <oid> and store the
 * result in <dst>, which is <len> bytes long.
 * Returns a pointer to the start of the string stored in <dst> on success,
 * or NULL on failure.
 */
char*
cnp_mib_oidtopath(cnp_mib_t *mib, cnp_oid_t oid, char *dst, size_t len)
{
	int ret, i, id;
	char tbuf[CNP_MIB_MAXNAMELEN];
	cnp_mib_node_t *node;

	dst[0] = '\0';

	if ((ret = pthread_rwlock_rdlock(&mib->mib_rwlock)) != 0) {
		cnp_log(cnp_logchan, CNP_LOG_ERR,
		    "failed to acquire read lock on MIB");
		return (NULL);
	}

	node = mib->mib_root;

	for (i = 0; i < CNP_MIB_OID_GETLEN(oid); i++) {
		id = CNP_MIB_OID_GETID(oid, i);
		if (id >= node->mn_childcnt) {
			cnp_log(cnp_logchan, CNP_LOG_ERR,
			    "invalid ID %d in OID", id);
			dst = NULL;
			break;
		}
		snprintf(tbuf, sizeof(tbuf), ".%s",
		    node->mn_children[id]->mn_name);
		if (strlcat(dst, tbuf, len) >= len) {
			break;
		}
		node = node->mn_children[id];
	}

	(void)pthread_rwlock_unlock(&mib->mib_rwlock);

	return (dst);
}

/*
 * cnp_mib_oidtostr()
 *
 * Generate the human-readable (string) version of the OID <oid> and store
 * the result in <dst>, which is <len> bytes long.
 * Returns a pointer to the start of the string stored in <dst> on success,
 * or NULL on failure.
 */
char*
cnp_mib_oidtostr(cnp_oid_t oid, char *dst, size_t len)
{
	char tbuf[4];
	int i;

	dst[0] = '\0';

	for (i = 0; i < CNP_MIB_OID_GETLEN(oid); i++) {
		snprintf(tbuf, sizeof(tbuf), ".%u", CNP_MIB_OID_GETID(oid, i));
		if (strlcat(dst, tbuf, len) >= len) {
			break;
		}
	}

	return (dst);
}

/*
 * cnp_mib_getbypath()
 *
 * Looks up the MIB node structure associated with the path <path>
 * and returns a pointer to that structure.  The MIB's read-write lock
 * should have been acquired before calling this function; it does not
 * perform any locking.
 * Returns a pointer to the MIB node associated with <oid> on success, or
 * NULL if the <oid> does not have an entry.
 */
static cnp_mib_node_t*
cnp_mib_getbypath(cnp_mib_t *mib, const char *path)
{
	int i, found;
	char pathbuf[CNP_MIB_MAXPATHLEN], *sp, *name;
	cnp_mib_node_t *node;

	if (strlcpy(pathbuf, path, sizeof(pathbuf)) >= sizeof(pathbuf)) {
		cnp_log(cnp_logchan, CNP_LOG_ERR,
		    "failed to get MIB node from path `%s': Path too long",
		    path);
		return (NULL);
	}

	node = mib->mib_root;
	sp = pathbuf;
	if (*sp == CNP_MIB_DELIM)
		sp++;

	if (*sp == '\0')
		return (node);

	do {
		name = sp;
		sp = strchr(sp, CNP_MIB_DELIM);
		if (sp != NULL)
			*(sp++) = '\0';

		found = 0;
		for (i = 0; i < node->mn_childcnt; i++) {
			if (strcmp(node->mn_children[i]->mn_name, name) == 0) {
				node = node->mn_children[i];
				found = 1;
				break;
			}
		}

		if (!found) {
			return (NULL);
		}

	} while (sp != NULL);

	return (node);
}

/*
 * cnp_mib_getbyoid()
 *
 * Looks up the MIB node structure associated with the object ID <oid>
 * and returns a pointer to that structure.  The MIB's read-write lock
 * should have been acquired before calling this function; it does not
 * perform any locking.
 * Returns a pointer to the MIB node associated with <oid> on success, or
 * NULL if the <oid> does not have an entry.
 */
static cnp_mib_node_t*
cnp_mib_getbyoid(cnp_mib_t *mib, cnp_oid_t oid)
{
	int mdepth;
	cnp_mib_node_t *node;

	if (CNP_MIB_OID_GETLEN(oid) > CNP_MIB_MAXOIDLEN)
		return (NULL);

	node = mib->mib_root;

	for (mdepth = 0; mdepth < CNP_MIB_OID_GETLEN(oid); mdepth++) {
		if (node->mn_childcnt <= CNP_MIB_OID_GETID(oid, mdepth)) {
			node = NULL;
			break;
		}

		node = node->mn_children[CNP_MIB_OID_GETID(oid, mdepth)];
	}

	return (node);
}

/*
 * cnp_mib_newnode()
 *
 * Allocate a new MIB node structure and set its type to <type> and name to
 * <name>.  If <name> is NULL, then no name is given to the node.
 * Returns a pointer to the newly allocated node on success, or NULL on
 * failure.  The returned pointer should later be freed using cnp_mib_freenode()
 * after use.
 */
static cnp_mib_node_t*
cnp_mib_newnode(cnp_mib_t *mib, enum cnp_mib_type type, enum cnp_mib_mode mode,
    const char *name, cnp_mib_node_t *parent)
{
	void *tmp;
	cnp_mib_node_t *node;

	cnp_log(cnp_logchan, CNP_LOG_DEBUG, "allocating new MIB node `%s'",
	    name);

	if ((node = cnp_malloc(sizeof(*node))) == NULL) {
		cnp_log(cnp_logchan, CNP_LOG_ERRNO,
		    "failed to allocate new MIB node");
		return (NULL);
	}

	memset(node, 0, sizeof(*node));

	if ((name != NULL) && (strlcpy(node->mn_name, name,
	    sizeof(node->mn_name)) >= sizeof(node->mn_name))) {
		cnp_log(cnp_logchan, CNP_LOG_ERR,
		    "invalid node name `%s': Name too long", name);
		cnp_free(node);
		return (NULL);
	}

	node->mn_type = type;
	node->mn_mode = mode;

	if (parent != NULL) {
		tmp = realloc(parent->mn_children,
		    (parent->mn_childcnt + 1) * sizeof(cnp_mib_node_t *));

		if (tmp == NULL) {
			cnp_log(cnp_logchan, CNP_LOG_ERRNO,
			    "failed to resize parent node's child table");
			cnp_mib_freenode(node);
			return (NULL);
		} else {
			cnp_log(cnp_logchan, CNP_LOG_DEBUG,
			    "attaching `%s' to `%s'",
			    node->mn_name, parent->mn_name);

			parent->mn_children = (cnp_mib_node_t **)tmp;
			parent->mn_children[parent->mn_childcnt++] = node;
			node->mn_parent = parent;
		}
	}

	return (node);
}


/*
 * cnp_mib_freenode()
 *
 * Free a MIB node structure.
 */
static void
cnp_mib_freenode(cnp_mib_node_t *node)
{
	int i;

	if (node != NULL) {
		/* first, recursively release all children */
		if (node->mn_type == CNP_MIB_TYPE_BRANCH) {
			for (i = 0; i < (int)node->mn_childcnt; i++)
				if (node->mn_children[i] != NULL)
					cnp_mib_freenode(node->mn_children[i]);

			cnp_free(node->mn_children);
		}

		cnp_free(node);
	}
}
